﻿using System;
using System.Linq;
using System.Data.Linq;
using System.Collections.Generic;
using System.Text;
using HR.Appointments.DataLayer;
using HR.Appointments.Service.DisplayLists;
using HR.Appointments.Service.ValueObjects;

namespace HR.Appointments.Service
{
    /// <summary>
    /// Calendar object to represent one day page in a regular calendar.
    /// </summary>
    public class CalendarBook
    {
        #region Member variable
        private OpenHoursEntity _openHours;
        private CompanyEntity _company;
        private List<ReservedTimeEntity> _reservedTime;
        private List<OrderEntity> _orders;
        private DateTime _calenderDate;
        #endregion 

        #region Constructors
        private CalendarBook(int companyId, DateTime date)
        {
            _calenderDate = date;
            _company = DAO.Instance.Company.GetInstance(companyId);
            _reservedTime = DAO.Instance.ReservedTime.GetByDate(companyId, date);
            _openHours = DAO.Instance.OpenHours.GetByDate(companyId, date);
            _orders = DAO.Instance.Order.GetByDate(companyId, date);
        }
        #endregion

        #region Properties
        /// <summary>
        /// A company entity that hold all company information for this object
        /// </summary>
        public CompanyEntity Company
        {
            get
            {
                return _company; 
            }
            set
            {
                _company = value; 
            }
        }

        /// <summary>
        /// List of reserved time for this date
        /// </summary>
        public List<ReservedTimeEntity> ReservedTime
        {
            get
            {
                return _reservedTime;
            }
            set
            {
                _reservedTime = value; 
            }
        }
        #endregion 

        #region Public Methods
        /// <summary>
        /// Gets a list of open hours on this date
        /// </summary>
        /// <returns>A list of timelist objects</returns>
        public List<TimeList> GetOpenHours()
        {
            return Convert(_openHours.GetTimeSlots(), null);
        }

        /// <summary>
        /// Get a list of reserved slots for a specific workstaiotn
        /// </summary>
        /// <param name="workstationId">The id of the workstation</param>
        /// <returns>A list of timelist objects</returns>
        public List<TimeList> GetReservedSlots(int workstationId)
        {
            var result = new List<TimeList>();

            var query = from data in _reservedTime
                        where data.WorkStationID == workstationId
                        select data;

            foreach (ReservedTimeEntity obj in query)
            {
                var timeList = obj.GetReservedSlots(_calenderDate);
                var entityList = Convert(timeList, obj.Station);
                result.AddRange(entityList);
            }
            return result;
        }

        /// <summary>
        /// Ge a list of all the booked slots in this calender 
        /// for a specific workstation
        /// </summary>
        /// <param name="workstationId">The id of the workstation</param>
        /// <returns>A list of timelist objects</returns>
        public List<TimeList> GetBookedSlots(int workstationId)
        {
            return GetSlots(OrderStatus.CONFIRMED, workstationId);
        }

        /// <summary>
        /// Get a list all the orders slots in this calender
        /// for a specific workstation.
        /// </summary>
        /// <param name="workstationId">The id of the workstation</param>
        /// <returns>A list of timelist objects</returns>
        public List<TimeList> GetOrderedSlots(int workstationId)
        {
            return GetSlots(OrderStatus.UNCONFIRMED, workstationId);
        }

        private List<TimeList> RemoveSlotsThatDoesntFitActivityDuration(List<TimeList> list, int activityId)
        {
            System.Diagnostics.Debug.Assert(_company != null, "Company is null");

            var activity = DAO.Instance.Activity.GetInstance(activityId);

            System.Diagnostics.Debug.Assert(activity != null, "Company is null");

            int dur = activity.Duration / _company.SlotDuration;

            //Create remove list of time that does't contain the nessicary duration
            List<TimeList> removeList = new List<TimeList>();
            //Loop through current list
            foreach (TimeList time in list)
            {
                //Create a compare list that contains items with nessicary duration
                List<TimeUnit> compareList = new List<TimeUnit>();
                var cmp = (TimeUnit)time.TimeUnit.Clone();
                for (int i = 0; i < dur; i++)
                {
                    compareList.Add((TimeUnit)cmp.Clone());
                    cmp = cmp.Add(_company.SlotDuration);
                }

                //Compare it to current list and add all time items that doesn't cut it
                //to a separete removelist to be processed after the loop.
                foreach (TimeUnit tu in compareList)
                { 
                    if(!list.Contains(new TimeList(tu, null, null)))
                    {
                        removeList.Add(time);
                    }
                }
            }
            //remove all items that didn't cut it.
            foreach (TimeList removeItem in removeList)
            {
                list.Remove(removeItem);
            }
            return list;
        }
        
        /// <summary>
        /// Get the free timeslots for a specific workstation doing a specific activity.
        /// </summary>
        /// <param name="workstationId">Workstation identifier</param>
        /// <param name="activityId">Activity identifier</param>
        /// <returns>A list of timelist objects</returns>
        public List<TimeList> GetVacantSlots(int workstationId, int activityId)
        {
            var vacantSlots = GetOpenHours();
            var reservedSlots = GetReservedSlots(workstationId);
            foreach (TimeList obj in reservedSlots)
            {
                if (vacantSlots.Contains(obj))
                    vacantSlots.Remove(obj);
            }

            var bookedOrders = GetBookedSlots(workstationId);
            foreach (TimeList obj in bookedOrders)
            {
                if (vacantSlots.Contains(obj))
                    vacantSlots.Remove(obj);
            }

            return RemoveSlotsThatDoesntFitActivityDuration(vacantSlots, activityId);
        }


        /// <summary>
        /// Get all orders that are confirmed on this date.
        /// </summary>
        /// <returns>A list of orders</returns>
        public List<OrderList> GetOrders()
        {
            var result = new List<OrderList>();
            var query = from o in _orders
                        where o.Status == OrderStatus.CONFIRMED
                        select o;

            foreach (OrderEntity obj in query)
            {
                result.Add(new OrderList(obj));
            }

            return result;
        }
        #endregion

        #region Private Methods
        private List<TimeList> GetSlots(OrderStatus booked, int workstationId)
        {
            var result = new List<TimeList>();
            var query = from o in _orders
                        where o.Status == booked
                        && o.WorkstationId == workstationId
                        select o;

            foreach (OrderEntity obj in query)
            {
                TimeUnit time = (TimeUnit)obj.FromTime.Clone();
                int duration = obj.Duration / _company.SlotDuration;
                for (int i = 0; i < duration; i++)
                {
                    var slot = new TimeList((TimeUnit)time.Clone(),
                        obj.Station, obj.Company);

                    result.Add(slot);
                    time.Add(_company.SlotDuration);
                }
            }

            return result;
        }

        private List<TimeList> Convert(List<TimeUnit> timeUnits,
            WorkstationEntity station)
        {
            var result = new List<TimeList>();
            foreach (TimeUnit time in timeUnits)
            {
                result.Add(new TimeList(time, station, null));
            }
            return result;
        }
        #endregion

        #region Static Methods
        /// <summary>
        /// Creates a new calender for a company at a specific date.
        /// </summary>
        /// <param name="companyId">The company id</param>
        /// <param name="date">The date which the calender should be opened</param>
        /// <returns>A new calender</returns>
        public static CalendarBook Open(int companyId, DateTime date)
        {
            return new CalendarBook(companyId, date);
        }
        #endregion
    }
}
